home *** CD-ROM | disk | FTP | other *** search
/ The Fatted Calf / The Fatted Calf.iso / Applications / Compression / Opener / Source / Controller.m < prev    next >
Text File  |  1993-08-15  |  24KB  |  1,052 lines

  1. /* 
  2.  * To add new filetypes:
  3.  *    The file Opener.table is a list of suffix/command pairs
  4.  *    that are used to identify and unpack various kinds of files.
  5.  *    You need to edit this table, and add an icon for the new type:
  6.  *  add ...
  7.  *    - a line to Opener.table, 
  8.  *           (formerly, new code in Controller.m:unpackFile())
  9.  *    - a utility in utils/..., and in Makefile.postamble
  10.  *    - an icon to the TIFF suitcase in Opener.nib
  11.  * - suffixes to 'types[]' in - openRequest
  12.  * and please mail me improvements if you do.
  13.  *
  14.  * Michael Hawley
  15.  * mike@media-lab.mit.edu
  16.  * Copyright (c) MIT Media Laboratory, September 1991
  17.  *
  18.  * Revisions:
  19.  *   new color icons
  20.  *   fixed case-insensitivity problem for archive file creation
  21.  *   fixed bug where app incorrectly recognized .ps and .eps extensions
  22.  *   fixed bug where app didn't properly prepare .PS and .PS for Preview
  23.  *   
  24.  *   added gz file type
  25.  *   added arj file type
  26.  *   method registerWindow:toPort: is obsolete, so treat it so
  27.  *   reconciled code to NeXTSTEP 3.1
  28.  *
  29.  *   added -removeTmpFiles, to clean up the scratch space.
  30.  *   added several new types (lzh, arc, zip, etc).
  31.  *   made it table-driven.
  32.  *   added kludge to try automatic unpacking of comp.sources material
  33.  *   added tar.Z-making function.
  34.  *
  35.  *   added preference panel, plus panel and code to permit selecting
  36.  *      an arbitrary archiving command.
  37.  *   added monitoring, "suggestion", defaults.
  38.  *   safe quoting of funny characters in filenames.
  39.  *   added archive-building commands.
  40.  *   fixed .tar.z problem
  41.  *   command output now generally goes to console, 
  42.  *      except "...done" messages, as when building an archive,
  43.  *      which pop up in a panel.
  44.  *   added hack in tmp() to fix the problem of "/tmp/O_foo.app"
  45.  *      directories not opening.
  46.  *
  47.  *   Thanks to Garance Drosehn (gad@eclipse.its.rpi.edu) for some
  48.  *   of these improvements.
  49.  */
  50.  
  51. #include <sys/types.h>
  52. #include <sys/stat.h>
  53. #import <dpsclient/dpsclient.h>
  54. #include <ctype.h>
  55. #import "Controller.h"
  56. #import "DefaultHandler.h"
  57. #import "DirPanel.h"
  58. #import "Process.h"
  59.  
  60. @implementation Controller
  61.  
  62. id _defaults;
  63. extern char TmpDir[];
  64.  
  65. int 
  66. err(a,b,c,d) char *a,*b,*c,*d; {
  67.     char t[1024];
  68.     extern id NXApp;
  69.     sprintf(t,a,b,c,d);
  70.     return NXRunAlertPanel([NXApp appName],t,0,0,0)==1;
  71. }
  72.  
  73. static id _pb2, me;
  74.  
  75. @interface Speaker(ObsoleteMethods)
  76. - (int)registerWindow:(int)windowNum toPort:(port_t)aPort;
  77. - (int)unregisterWindow:(int)windowNum;
  78. @end
  79.  
  80. - setPackButton2:b {
  81.     packButton2 = _pb2 = b;
  82.     return self;
  83. }
  84.  
  85. #include "monitor.m"
  86.  
  87. char *
  88. tmp(char *s,  char *suffix, int isDir) {
  89. /* 
  90.  * return a path for a temporary file
  91.  * named "/tmp/O_[n_]file", where 'file' is 's';
  92.  * e.g., /a/b/foo.bar, .bar => /tmp/O_foo
  93.  * if 'isDir', map a "." in the suffix to "_" to ensure that
  94.  * the directory wrapper will open properly.
  95.  */
  96.     static char t[1024];
  97.     char *p, u[1024];
  98.     int k = 1;
  99.     
  100.     strcpy(u,s);
  101.     if (*suffix) u[strlen(u) - strlen(suffix)] = '\0';
  102.     if (p = rindex(u,'/')) p++;
  103.     else p = s;
  104.     if (isDir) {
  105.         char *x = rindex(p,'.');
  106.         if (x) *x = '_';
  107.     }
  108.     sprintf(t,"%s/O_%s",TmpDir,p);
  109.     while (access(t,0)==0) sprintf(t,"%s/O_%d_%s",TmpDir,k++,p);
  110.     return t;
  111. }
  112.  
  113. char *
  114. path(char *s)
  115. /*
  116.  * if 's' is in [NXArgv[0]], return [NXArgv[0]]/s.
  117.  * (for looking up internal commands and files in .../Opener.app/...).
  118.  */
  119. {
  120.     char t[1024], *q, *r;
  121.     static char p[1024];
  122.     extern char **NXArgv;
  123.     *t = '\0';
  124.     sscanf(s,"%s",t);
  125.     if (!*t || *t=='(') return s;
  126.     r = s + strlen(t);
  127.     strcpy(p,*NXArgv);
  128.     if (q=rindex(p,'/'))
  129.         strcpy(q+1,t);
  130.     else
  131.         strcpy(p,t);
  132.     if (access(p,0)==0){
  133.         strcpy(p+strlen(p),r);
  134.         return p;
  135.     }
  136.     return s;
  137. }
  138.  
  139. char *
  140. skipsp(char *s) {
  141.     while (*s==' ' || *s=='\t' || *s == '\n') ++s;
  142.     return s;
  143. }
  144.  
  145. int
  146. blank(char *s) { /* true if 's' is blank */
  147.     while (*s == ' ' || *s=='\t' || *s == '\n') ++s;
  148.     return !*s;
  149. }
  150.  
  151. char *
  152. save(char *s) {
  153.     char *p = (char *)malloc(strlen(s)+1);
  154.     if (p) strcpy(p,s);
  155.     return p;
  156. }
  157.  
  158. void
  159. stripcomment(s) char *s; { /* truncate 's' at a comment character ('#') */
  160.     char *p = index(s,'#');
  161.     if (p && (p==s || p[-1] != '\\')) *p = '\0';
  162. }
  163.  
  164. char *
  165. strindex(char *s, char *t) { /* return ptr to first match of 't' in 's' */
  166.     int n = strlen(t);
  167.     
  168.     if (s)
  169.         while (*s)
  170.             if (!strncmp(s, t, n)) return s;
  171.             else s++;
  172.     return (char *)0;
  173. }
  174.  
  175. // Case-insensitive version of equal().  
  176. // Commented out by Denise Blakeley 08/93 Opener 3.1.1.
  177. // int 
  178. // equal(char *s, char *t) { /* true if 's' & 't' are equal (case-insens.) */
  179. //    char a, b;
  180. //    while (*s && *t){
  181. //        a = isupper(*s)? tolower(*s) : *s;
  182. //        b = isupper(*t)? tolower(*t) : *t;
  183. //        s++, t++;
  184. //        if (a != b) return 0;
  185. //    }
  186. //    return  (*s || *t)? 0 : 1;
  187. // }
  188.  
  189. /* Case-sensitive version of equal(). */
  190. /* Added by Blakeley 08/93 Opener 3.1.1. */
  191. int 
  192. equal(char *s, char *t) { /* true if 's' & 't' are equal (case-sens.) */
  193.     char a, b;
  194.     while (*s && *t){
  195.         a = *s;
  196.         b = *t;
  197.         s++, t++;
  198.         if (a != b) return 0;
  199.     }
  200.     return  (*s || *t)? 0 : 1;
  201. }
  202.  
  203. int 
  204. tailmatch(char *s, char *t){ /* true if t appears at end of s */
  205.     int tn = strlen(t), sn = strlen(s);
  206.     if (tn > sn) return 0;
  207.     return equal(s+(sn-tn), t);
  208. }
  209.  
  210. char *
  211. shstrcat(char *s, char *t){ // strcat, but quote shell metachars
  212.     register char *p;
  213.     
  214.     while (*s) s++;
  215.     p=t;
  216.     if (*p=='~') *s++='\\';    // csh "feature"
  217.     while (*p) {
  218.         if (*p<=' '||*p>'~'||index(";&()|<>\\'\"$*?[]^!", *p)) *s++='\\';
  219.         *s++=*p++;
  220.     }
  221.     *s='\0';
  222.     return t;
  223. }
  224.  
  225. void
  226. subfname(char *s, char *a, char *b) { /* like 'substr', but assumes the "b"
  227.                       string is a filename that may need
  228.                       special-to-shell characters quoted */
  229.     char q[8192], quoted_b[2048];
  230.     char *p = s;
  231.     int n = strlen(a);
  232.     quoted_b[0] = '\0';
  233.     shstrcat(quoted_b, b);
  234.     for (;*p;p++){
  235.         if (*p == *a && strncmp(p,a,n)==0){
  236.             strcpy(q,p+n);
  237.             strcpy(p,quoted_b);
  238.             strcpy(p+strlen(quoted_b),q);
  239.             p += strlen(quoted_b)-1;
  240.         }
  241.     }
  242. }
  243.  
  244. #define MaxLen 256
  245. #define MaxSuf 64
  246. #define MaxTab 64
  247.  
  248. char Suffix[MaxTab][MaxSuf],
  249.      Command[MaxTab][MaxLen];
  250. int NT = 0;
  251.  
  252. char PSuffix[MaxTab][MaxSuf],
  253.      PCommand[MaxTab][MaxLen];
  254. int PNT = 0;
  255.  
  256. int fdTime(int f){ /* return size of file 'f' */
  257.     struct stat b;
  258.     fstat(f, &b);
  259.     return b.st_mtime;
  260. }
  261.  
  262. void
  263. clearPackItems() {
  264.     int i, n = [[_pb2 target] count];
  265.     for (i=0;i<n;i++){
  266.         [[_pb2 target] removeItemAt:i];
  267.     }
  268. }
  269.  
  270. void
  271. addPackItem(char *s) {
  272.     id c;
  273.     c = [[_pb2 target] addItem:s];
  274.     [c setTarget:me]; 
  275.     [c setAction:@selector(setPackSuffix:)];
  276. }
  277.  
  278. void
  279. resetPackItems() {
  280.     int i;
  281.     clearPackItems();
  282.     for (i=0;i<PNT;i++)
  283.         addPackItem(PSuffix[i]);
  284. }
  285.  
  286. void
  287. addUnpackItem(char *s) {
  288.     id c;
  289.     c = [[_pb2 target] addItem:s];
  290.     [c setTarget:me]; 
  291.     [c setAction:@selector(setUnpackSuffix:)];
  292. }
  293.  
  294. void
  295. loadTable() {
  296. /*
  297.  * Unpacking files is table-driven.
  298.  * The file "Opener.table" is of the form
  299.  *    suffix command
  300.  * like
  301.  *    .Z    uncompress < $f > $t
  302.  * When opening files, the table is checked to
  303.  * find a matching command for the given suffix.
  304.  */
  305.     char s[1024];
  306.     FILE *f;
  307.     static int first = 1;
  308.     static int t = 0;
  309.     int pack = 0;
  310.     
  311.     strcpy(s,path("Opener.table"));
  312.     if (!first){
  313.         if (f=fopen(s,"r")){
  314.             int tt = fdTime(fileno(f));
  315.             fclose(f);
  316.             if (tt == t) return;
  317.             t = tt;                 // table has changed -- reread it
  318.             NT=PNT=0;
  319.             clearPackItems();
  320.             printf("rereading table\n");
  321.         } else
  322.             return;
  323.     }
  324.     first = 0;
  325.     if (f = fopen(s,"r")){
  326.         char *p, *q;
  327.         t = fdTime(fileno(f));
  328.         while (fgets(s,sizeof s,f)){
  329.             stripcomment(s);
  330.             if (blank(s)) continue;
  331.             if (strncmp(s,"Unpack:",7)==0) continue;
  332.             if (strncmp(s,"Pack:",5)==0){ pack++; continue; }
  333.             if (pack){
  334.                 for (p=skipsp(s), q=PSuffix[PNT]; 
  335.                     *p && *p != ' ' && *p != '\t' && (q-PSuffix[PNT])<(MaxSuf-1);
  336.                     *q++ = *p++) ;
  337.                 *q = '\0';
  338.                 for (p = skipsp(p), q=PCommand[PNT];
  339.                     *p && (*p != '\n') && (q-PCommand[PNT])<(MaxLen-1);
  340.                     *q++ = *p++) ;
  341.                 *q='\0';
  342.                 addPackItem(PSuffix[PNT]);
  343. // printf("[%s]: %s\n",PSuffix[PNT],PCommand[PNT]);
  344.                 if (!blank(PSuffix[PNT])&& !blank(PCommand[PNT]) && PNT < MaxTab)
  345.                     ++PNT;
  346.             } else {
  347.                 for (p=skipsp(s), q=Suffix[NT]; 
  348.                     *p && *p != ' ' && *p != '\t' && (q-Suffix[NT])<(MaxSuf-1);
  349.                     *q++ = *p++) ;
  350.                 *q = '\0';
  351.                 for (p = skipsp(p), q=Command[NT];
  352.                     *p && (*p != '\n') && (q-Command[NT])<(MaxLen-1);
  353.                     *q++ = *p++) ;
  354.                 *q='\0';
  355. // printf("[%s]: %s\n",Suffix[NT],Command[NT]);
  356.                 if (!blank(Suffix[NT]) && !blank(Command[NT]) && NT < MaxTab)
  357.                     ++NT;
  358.             }    
  359.         }
  360.         fclose(f);
  361.     } else {
  362. #define C(a,b) strcpy(Suffix[NT],a), strcpy(Command[NT],b), NT++
  363.         C(".tar", "unpack: tar xf $f");
  364.         C(".shar","unpack: sed '1,/[^:#]/d' $f | /bin/sh");
  365.         C(".uu",  "unpack: uudecode < $f; open *");
  366.         C(".lzh", "unpack: $p/xlharc xf $f");
  367.         C(".arc", "unpack: $p/arc ox $f");
  368.         C(".sit", "unpack: $p/unsit -u $f");
  369.         C(".bin", "unpack: $p/unsit -u $f");
  370.         C(".hqx", "unpack: $p/mcvert $f; $p/unsit *.sit.*");
  371.         C(".zip", "unpack: $p/unzip $f");
  372.         C(".zoo", "unpack: $p/booz x $f");
  373.         C(".Z",   "uncompress < $f > $t");
  374. #undef C
  375. #define C(a,b) strcpy(PSuffix[PNT],a), strcpy(PCommand[PNT],b), addPackItem(a), PNT++
  376.         C(".tar", "(cd $d; tar cf - $F > $t.tar; echo $t.tar done)");
  377.     }
  378. }
  379.  
  380. int
  381. findSuffix(char *s) {
  382.     char *p = s + strlen(s);
  383.     int i;
  384.     loadTable();
  385.     for (i=0;i<NT;i++)
  386.         if (equal(Suffix[i],p-strlen(Suffix[i])))
  387.             /* changed from equal() by Blakeley */
  388.             return i;
  389.     return -1;
  390. }
  391.  
  392. int
  393. findPSuffix(char *s) {
  394.     char *p = s + strlen(s);
  395.     int i;
  396.     loadTable();
  397.     for (i=0;i<PNT;i++)
  398.         if (equal(PSuffix[i],p-strlen(PSuffix[i]))) 
  399.             /* changed from equal() by Blakeley */
  400.             return i;
  401.     return -1;
  402. }
  403.  
  404. void
  405. ex_unpack(char *s) {
  406.     char t[1024];
  407.     if (strncmp(s,"unpack: ",8)) return;
  408.     strcpy(t,skipsp(s+8));
  409.     strcpy(s,"mkdir $t; cd $t; "); strcat(s,t);
  410. }
  411.  
  412. void
  413. substr(char *s, char *a, char *b) { /* like 'sub', but with strings */
  414.     char q[8192];
  415.     char *p = s;
  416.     int n = strlen(a);
  417.     for (;*p;p++){
  418.         if (*p == *a && strncmp(p,a,n)==0){
  419.             strcpy(q,p+n);
  420.             strcpy(p,b);
  421.             strcpy(p+strlen(b),q);
  422.             p += strlen(b)-1;
  423.         }
  424.     }
  425. }
  426.  
  427. void
  428. expand(char *s, int i, char *c) {
  429. /*
  430.  * 's' is the current filename.
  431.  * 'i' is the index in the Suffix/Command table.
  432.  * 'c' is the command to fill;
  433.  * Replace "unpack: ..." with the standard unpack command
  434.  *         $f with the filename, 
  435.  *         $t with the temp
  436.  */
  437.     char *T, *p, S[MaxSuf], q[1024], *r;
  438.     int isDir = 0;
  439.     
  440.     strcpy(c,Command[i]);
  441.     strcpy(S,s+strlen(s)-strlen(Suffix[i]));
  442.     if (strindex(c,"mkdir $t") || strindex(c,"unpack: ")) isDir = 1;
  443.     T = tmp(s,Suffix[i],isDir); 
  444.     
  445.     ex_unpack(c);     /* rewrite "unpack: ..." */
  446.     
  447.     p = c;          /* prepend the $p/... paths */
  448.     while (p = strindex(p,"$p/")){
  449.         sscanf(p,"%s",q);
  450.         r = path(q+3);
  451.         substr(p,q,r);
  452.     }
  453.     
  454.     if (!strindex(c,"open ")){
  455.         if (isDir) strcat(c,"; (cd $t; open . )"); // make sure directory opens
  456.         else strcat(c,"; open $t");
  457.     }
  458.     
  459.     subfname(c,"$f",s); /* expand the abbreviations */
  460.     subfname(c,"$t",T);
  461.     subfname(c,"$s",S);
  462. }
  463.  
  464. void
  465. getTmpName(char *s, char *t) {
  466. /*
  467.  * Copy a /tmp name for the comp.sources archive into 't'.
  468.  * if 's' is ...comp.sources/.../foo/part00.Z ==> /tmp/O_foo
  469.  *           ...comp.sources/.../foo.Z        ==> /tmp/O_foo
  470.  */
  471.     char *p = rindex(s,'/'), q[1024], c;
  472.     int n;
  473.  
  474.     if (!p) return (void)strcpy(t,s);
  475.     strcpy(q,p+1);
  476.     c = q[4]; q[4]='\0';
  477.  
  478.     if (equal(q,"part") && isdigit(c)){ /* skip to previous */
  479.         *p='\0';
  480.         p = rindex(s,'/');
  481.         if (!p) p=s;
  482.         else p++;
  483.     } else p++;
  484.  
  485.     strcpy(t,p);
  486.     n = findSuffix(t);
  487.     p = (n== -1)? ".Z" : Suffix[n];
  488.     if (t[strlen(t)-strlen(p)]=='.')
  489.         t[strlen(t)-strlen(p)] = '\0';
  490.     strcpy(t,tmp(t,p,0));
  491. }
  492.  
  493. int 
  494. System(fmt, a,b,c,d,e,f,g) char *fmt,*a,*b,*c,*d,*e,*f,*g; {
  495.     char t[8192];
  496.     sprintf(t,fmt,a,b,c,d,e,f,g);
  497.     printf("! %s\n",t);
  498.     return system(t);
  499. }
  500.  
  501. void 
  502. openFile(char *s){ /* open 's' in workspace */
  503.     int ok = 0;
  504.     id p = [NXApp appSpeaker];
  505.     
  506.     [p setSendPort:NXPortFromName(NX_WORKSPACEREQUEST, NULL)];
  507.     [p openTempFile:s ok:&ok];
  508.     if (!ok) err("Couldn't open file: %s",s);
  509. }
  510.  
  511. void 
  512. previewFile(char *s) { /* open 's' in the default PostScript previewer */
  513. /* This function worked over for 3.1.1 by Blakeley; it never worked before
  514.  * but it does now. */
  515.     char *a = (char *)0; 
  516.     char *index;
  517.     char head[1024];
  518.     int ok = 0;
  519.     id p = [NXApp appSpeaker];
  520.     
  521.     /* get handle on the user's favorite Preview application */
  522.     if (!a) a = "Preview";
  523.     [p setSendPort:NXPortFromName(a, NULL)];
  524.     
  525.     getTmpName(s, head);
  526.     index = rindex(&head[0], '.');    
  527.     if (tailmatch(s, ".PS"))
  528.         strcpy(index+1, "ps");
  529.     else
  530.         strcpy(index+1, "eps");
  531.     
  532.     System("cp %s %s", s, head);
  533.     [p openFile: head ok:&ok];
  534.     if (!ok) err("Couldn't open file: %s",s);
  535. }
  536.  
  537. int 
  538. confirm(a,b,c,d) char *a,*b,*c,*d; {
  539.     char t[1024];
  540.     extern id NXApp;
  541.     sprintf(t,a,b,c,d);
  542.     return NXRunAlertPanel([NXApp appName],t,"Yes","No",NULL)==1;
  543. }
  544.  
  545. - setTmp:sender {
  546.     char *s = (char *)[sender stringValueAt:0];
  547.     if (!s || !*s || access(s,0)){
  548.         if (s && *s) err("Couldn't access '%s'",s);
  549.         [sender setStringValue:TmpDir at:0];
  550.         s = (char *)[sender stringValueAt:0];
  551.     } else
  552.         strcpy(TmpDir,s);
  553.     [_defaults writeDefaults:self];
  554.     return self;
  555. }
  556.  
  557. int
  558. unpackFile(s) char *s; {
  559.     int i;
  560.     char c[2048];
  561.     i = findSuffix(s);
  562.     if (i != -1){ /* we found a suffix and command in the table */
  563.         expand(s,i,c);
  564.         [me system:c]; // System("(%s)&",c);
  565.     } else
  566.     if (tailmatch(s,".PS" ) || tailmatch(s,".EPS")) /* catch some PS files */
  567.         previewFile(s);
  568.     else
  569.         err("Couldn't open '%s'",s);
  570.     return 1;
  571. }
  572.  
  573. - (BOOL)appAcceptsAnotherFile:sender {
  574.     return YES;
  575. }
  576.  
  577. int 
  578. isCompSourceArchive(s) char *s; {
  579.     return ((strindex(s,"comp.sources") || strindex(s,"mod.sources") ||
  580.                 strindex(s,"net.sources")) &&
  581.         equal(s+strlen(s)-2,".z") &&
  582.         !strindex(s,"index") && !strindex(s,"Index") && !strindex(s,"INDEX") &&
  583.         !strindex(s,"files") && !strindex(s,"Files") && !strindex(s,"FILES"));
  584. }
  585.  
  586.  
  587. #define MaxPend 512
  588. char *Pending[MaxPend];
  589. int NP = 0;
  590.  
  591. void 
  592. reap(DPSTimedEntry n, double now, char *userData){
  593.     int i, np;
  594.     char x[1024], head[1024], c[8192], *p, q[1024], *r;
  595.     
  596.     DPSRemoveTimedEntry(n);
  597.     np = NP;
  598.     if (!NP) return;
  599.     NP = 0;
  600.     
  601.     if (confirm("Try to build %s...?",Pending[0])){
  602.     strcpy(x,Pending[0]);
  603.         getTmpName(x,head);
  604.     sprintf(c,"(mkdir $t; cp ");
  605.     for (i=0; i<np; i++) 
  606.         strcat(c,Pending[i]), strcat(c," ");
  607.     sprintf(c+strlen(c)," $t; $p/builder $t; open $t $t/errors)&");
  608.     
  609.     p = c;          /* prepend the $p/... paths */
  610.     while (p = strindex(p,"$p/")){
  611.         sscanf(p,"%s",q);
  612.         r = path(q+3);
  613.         substr(p,q,r);
  614.     }
  615.     substr(c,"$t",head);
  616.     System("%s",c);
  617.     } else {
  618.     for (i=0;i<np;i++) 
  619.         unpackFile(Pending[i]);
  620.     }
  621.     for (i=0;i<np;i++) free(Pending[i]);
  622. }
  623.  
  624. void
  625. pending(s) char *s;
  626. /*
  627.  * One or more files from a comp.sources archive are being selected.
  628.  * Put them on a queue -- reap() will unpack them specially.
  629.  */
  630. {
  631.     if (NP == MaxPend)
  632.         return (void)err("Too many pending files!  Try quitting.");
  633.     Pending[NP++] = save(s);
  634.     DPSAddTimedEntry(4.0, (DPSTimedEntryProc)reap, 0, 10);
  635. }
  636.  
  637. - (int)appOpenFile:(const char *)filename type:(const char *)aType {
  638.     me = self;
  639.     if (isCompSourceArchive(filename))
  640.         pending(filename);
  641.     else
  642.         unpackFile((char *)filename);
  643.     return 1;
  644. }
  645.  
  646. - openRequest:sender {
  647.     const char *directory, *const *files;
  648.     static const char *const types[] = {
  649.             "tar", "TAR", "tar.z", "tar.Z", "TAR.Z", "tar-z", "tar-Z", "TAR-Z",
  650.             "tar.gz", "tar.GZ", "TAR.GZ", "tar-gz", "tar-GZ", "TAR-GZ",
  651.             "shar","SHAR","shar.z","shar.Z","SHAR.Z","shar-z","shar-Z","SHAR-Z",
  652.             "shar.gz", "shar.GZ", "SHAR.GZ", "shar-gz", "shar-GZ", "SHAR-GZ",
  653.             "PS", "EPS", "PS-Z", "ps-Z", "ps-z", "EPS-Z", "eps-Z", "eps-z",
  654.             "PS-GZ", "ps-GZ", "ps-gz", "EPS-GZ", "eps-GZ", "eps-gz",
  655.             "arc", "Arc", "ARC",
  656.             "bin", "Bin", "BIN",
  657.             "lzh", "Lzh", "LZH",
  658.             "hqx", "Hqx", "HQX",
  659.             "sit", "Sit", "SIT",
  660.             "zip", "Zip", "ZIP",
  661.             "zoo", "Zoo", "ZOO",
  662.             "uu", "UU",
  663.             "z", "Z",
  664.             "gz", "Gz", "GZ",
  665.             "arj", "Arj", "ARJ",
  666.             "compressed", "Compressed", "COMPRESSED",
  667.             NULL};
  668.     id p = [[OpenPanel new] allowMultipleFiles:YES];
  669.     char name[512];
  670.     
  671.     if ([p runModalForTypes:types]){
  672.         files = [p filenames];        // list of multiple files
  673.         directory = [p directory];
  674.         while (files && *files) {
  675.             strcpy(name, directory );
  676.             strcat(name, "/" );
  677.             strcat(name, *files );
  678.             files++;
  679.             unpackFile(name);
  680.         }
  681.     }
  682.     return self;
  683. }
  684.  
  685. - (BOOL)scratchFiles {
  686.     char t[1024];
  687.     FILE *p;
  688.     char s[1024];
  689.     BOOL b = NO;
  690.     
  691.     if (strcmp(TmpDir,"/tmp"))
  692.         sprintf(t,"ls /tmp/O_* %s/O_*",TmpDir);
  693.     else
  694.         sprintf(t,"ls /tmp/O_*");
  695.     p = popen(t,"r");
  696.     if (p){
  697.         if (fgets(s,sizeof s,p) && strncmp(s,"No match",8)) b = YES;
  698.         pclose(p);
  699.     }
  700.     return b;
  701. }
  702.  
  703. - removeTmpFiles:sender {
  704.     if ([self scratchFiles] && 
  705.         confirm("Remove the scratch files in %s?",TmpDir)){
  706.         if (strcmp(TmpDir,"/tmp"))
  707.             System("rm -rf /tmp/O_* %s/O_* &",TmpDir);
  708.         else
  709.             System("rm -rf /tmp/O_* &");
  710.     }
  711.     return self;
  712. }
  713.  
  714. - appWillTerminate:sender {
  715.     unsigned int wn;
  716.     NXConvertWinNumToGlobal([[NXApp appIcon] windowNum], &wn);
  717.     [self removeTmpFiles:sender];
  718.     [[NXApp appSpeaker] unregisterWindow:wn];
  719.     return self;
  720. }
  721.  
  722. - appDidInit:sender {
  723.     char subj[512] = "Opener-";
  724.     unsigned int wn;
  725.     id s = [NXApp appSpeaker];
  726.     
  727.     me = self;
  728.     _defaults = [DefaultHandler new];
  729.     [tmpDir setStringValue:TmpDir at:0];
  730.     
  731.     strcat(subj,[version stringValue]);
  732.     strcat(subj,"-newuser");
  733.     OpenerMonitor(subj);
  734.     
  735.     NXConvertWinNumToGlobal([[NXApp appIcon] windowNum], &wn);
  736.     [s setSendPort:NXPortFromName(NX_WORKSPACEREQUEST, NULL)];
  737.     [s registerWindow:wn toPort:[[NXApp appListener] listenPort]];
  738.     return self;
  739. }
  740.  
  741. char *iconPathList = (char *)0;
  742.  
  743. - (int)iconEntered:(int)windowNum at:(double)x :(double)y
  744.     iconWindow:(int)iconWindowNum 
  745.     iconX:(double)iconX iconY:(double)iconY
  746.     iconWidth:(double)iconWidth 
  747.     iconHeight:(double)iconHeight
  748.     pathList:(char *)pathList
  749. {
  750.     if (!iconPathList || strcmp(iconPathList, pathList)) {
  751.         free(iconPathList);
  752.         iconPathList = save(pathList);
  753.     }
  754.     return 0;
  755. }
  756.  
  757. void
  758. stot(char *s, char **t, char c) { /* split 's' into a table 't' */
  759.     char *p;
  760.     while (p = index(s,c)){
  761.         *p++ = '\0';
  762.         *t++ = s;
  763.         s = p;
  764.     }
  765.     *t++ = s;
  766.     *t = (char *)0;
  767. }
  768.  
  769. void
  770. trimdir(char *d, char **t) { /* put longest leading directory of t into d */
  771.     int i, n;
  772.     char *p,*q;
  773.     
  774.     *d = '\0';
  775.     p = rindex(t[0],'/');
  776.     if (p){
  777.         *p='\0';
  778.         strcpy(d,t[0]); strcat(d,"/");
  779.         *p='/';
  780.     } else return ;
  781.  
  782.     if (t[1]){
  783.         for (i=0;t[i];i++){
  784.             for (p=d,q=t[i]; *p && *q && *p == *q; p++, q++);
  785.             if (!*p) continue;
  786.             *p = '\0';
  787.         }
  788.     }
  789.     n = strlen(d);
  790.     for (i=0;t[i];i++) t[i] += n;
  791. }
  792.  
  793. - processOutput:(char *)s {
  794.     err("%s",s);
  795.     return self;
  796. }
  797.     
  798. - system:(char *)s { // exec 's' and processOutput pops up a panel with output
  799.     [Process new:s delegate:self];
  800.     return self;
  801. }
  802.  
  803. void
  804. pexpand(i,c,d,f,F,t)
  805.     char *c, *d, *f, *F, *t;
  806.     int i;
  807. /*
  808.  * like expand, but for packing:
  809.  * 'i' is the index in the Suffix/Command table.
  810.  * 'c' is the command to fill;
  811.  * 'd' is the prefix directory
  812.  * 'F' is the files without 'd' removed
  813.  * 'f' is the files with 'd' removed
  814.  * 't' is the files with 'd' removed
  815.  */
  816. {
  817.     char *p, q[1024], *r; 
  818.     strcpy(c,PCommand[i]);
  819.     
  820.     p = c;          /* prepend the $p/... paths */
  821.     while (p = strindex(p,"$p/")){
  822.         sscanf(p,"%s",q);
  823.         r = path(q+3);
  824.         substr(p,q,r);
  825.     }
  826.     
  827.     substr(c,"$F",F);
  828.     substr(c,"$f",f);
  829.     substr(c,"$d",d);
  830.     substr(c,"$t",t);
  831. }
  832.  
  833.  
  834. void
  835. expand_files(char *s, char *p, char *dir) {
  836.     char *t[1024], x[8192];
  837.     int i;
  838.     strcpy(x,s);
  839.     stot(x,t,'\t');
  840.     if (dir) trimdir(dir,t);
  841.     *p = '\0';
  842.     for (i=0;t[i];i++){
  843.         if (i > 0) strcat(p," ");
  844.         shstrcat(p,t[i]);
  845.         p += strlen(p);
  846.     }
  847. }
  848.  
  849. - setDirectory:sender {
  850.     id p = [SavePanel new];
  851.     char *s;
  852.     
  853.     [[[p contentView]
  854.     findViewWithTag:NX_OPTITLEFIELD]
  855.     setStringValue:"Unpack files in:"];
  856.     
  857.     if ([p dirPanelRunModal] && (s = (char *)[p directory])){
  858.         [tmpDir setStringValue:s at:0];
  859.         strcpy(TmpDir,s);
  860.         [_defaults writeDefaults:self];
  861.     }
  862.     return self;
  863. }
  864.  
  865. static char Files[8192];
  866.  
  867. char *
  868. getText(id t, char *s) {
  869.     id d = [t docView];
  870.     int n = [d textLength];
  871.     char *p;
  872.     *s = '\0';
  873.     [d getSubstring:s start:0 length:[d textLength]];
  874.     s[n] = '\0';
  875.     for (p=s; *p; p++) if (*p == '\n') *p = ' ';
  876.     return s;
  877. }
  878.  
  879. void
  880. setText(id t, char *s) {
  881.     id w = [t window], d = [t docView];
  882.     static NXPoint origin = {0.0,0.0};
  883.     [w disableFlushWindow];
  884.     [d setMonoFont:YES];
  885.     [d setText:s];
  886.     [d setSel:0:999999];
  887.     [d setSelGray:0.0];
  888.     [d selectNull];
  889.     [d scrollPoint:&origin];
  890.     [t display];
  891.     [[w reenableFlushWindow] flushWindow];
  892.     NXPing();
  893. }
  894.  
  895. int
  896. setSuffixes(char *s) {
  897.     char *p = s + strlen(s);
  898.     int i, r = -1;
  899.     loadTable();
  900.     clearPackItems();
  901.     for (i=0;i<NT;i++)
  902.         if (equal(Suffix[i],p-strlen(Suffix[i]))){
  903.             if (r == -1) r = i;
  904.             addUnpackItem(Suffix[i]);
  905.         }
  906.     if (r >= 0) [[_pb2 selectedCell] setTitle:Suffix[r]];
  907.     return r;
  908. }
  909.  
  910. char MoreFiles[8192] = "";
  911. int Reset = 1;
  912.  
  913. - setPackCommand {
  914.     char t[1024], f[4096], F[4096], dir[1024], *p, files[8192];
  915.     char c[8192], *curSuffix, *curCmd;
  916.     int i;
  917.     
  918.     Again:
  919.         curSuffix = (char *)[[packButton2 selectedCell] title];
  920.         curCmd = (char *)[[packButton selectedCell] title];
  921.         strcpy(files,Files);
  922.         
  923.         if (strcmp(curCmd,"create")==0){
  924.             if (Reset) resetPackItems();
  925.             Reset = 1;
  926.             curSuffix = (char *)[[packButton2 selectedCell] title];
  927.             i = findPSuffix(curSuffix);
  928.             if (i == -1){
  929.                 [packPanel orderOut:self];
  930.                 err("Unknown archive format: %s",curSuffix);
  931.                 return 0;
  932.             }
  933.         
  934.             expand_files(files,f,dir);
  935.             expand_files(files,F,0);
  936.             sscanf(f,"%s",t); stripnl(t);
  937.             if (p = rindex(t,'/')) strcpy(t,p+1);
  938.             strcat(t,curSuffix);
  939.             p = tmp(t,"",0); strcpy(t,p);
  940.         
  941.             pexpand(i,c,dir,f,F,t);
  942.             setText(packCmdText,c);
  943.         } else {
  944.             if (index(files,'\t')){
  945.                 char *p;
  946.                 p = index(files,'\t');
  947.                 *p = '\0';
  948.                 strcpy(MoreFiles,p+1);
  949.                 stripnl(files);
  950.             } else {
  951.                 *MoreFiles = '\0';
  952.             }
  953.             expand_files(files,f,0);
  954.             i = setSuffixes(files);
  955.             if (i == -1){
  956.                 extern void NXBeep();
  957.                 [[packButton selectedCell] setTitle:"create"];
  958.                 NXBeep();
  959.                 resetPackItems();
  960.                 NXPing();
  961.                 goto Again;
  962.             }
  963.             
  964.             expand(f,i,c);
  965.             setText(packCmdText,c);
  966.         }
  967.     return self;
  968. }
  969.  
  970. - setPackOrUnpack:sender {
  971.     char *s = (char *)[[sender selectedCell] title];
  972.     [[packButton selectedCell] setTitle:s];
  973.     if (strcmp(s,"create")==0)
  974.         [packPanel setTitle:"Make new archive"];
  975.     else
  976.         [packPanel setTitle:"Unpack archive"];
  977.     resetPackItems();
  978.     [self setPackCommand];
  979.     return self;
  980. }
  981.  
  982. - setUnpackSuffix:sender {
  983.     char *s = (char *)[[sender selectedCell] title];
  984.     [[packButton2 selectedCell] setTitle:s];
  985.     NXPing();
  986.     [self setPackCommand];
  987.     return self;
  988. }
  989.  
  990. - setPackSuffix:sender {
  991.     char *s = (char *)[[sender selectedCell] title];
  992.     extern char ArchiveFormat[];
  993.     strcpy(ArchiveFormat,s);
  994.     [_defaults writeDefaults:self];
  995.     [[packButton2 selectedCell] setTitle:s];
  996.     Reset = 0;                         // bandaid
  997.     NXPing();
  998.     [self setPackCommand];
  999.     return self;
  1000. }
  1001.  
  1002. - packFiles:(char *)files {
  1003.     strcpy(Files,files);
  1004.     if (!*MoreFiles){
  1005.         if (index(Files,'\t') || findSuffix(Files) == -1){
  1006.             [packPanel setTitle:"Make new archive"];
  1007.             [[packButton selectedCell] setTitle:"create"];
  1008.         } else {
  1009.             [packPanel setTitle:"Unpack archive"];
  1010.             [[packButton selectedCell] setTitle:"unpack"];
  1011.         }
  1012.     }
  1013.     [packPanel makeKeyAndOrderFront:self];
  1014. /*    Reset = 0; */
  1015.     [self setPackCommand];
  1016.     return self;
  1017. }
  1018.  
  1019. - cancelPack:sender {
  1020.     [packPanel orderOut:sender];
  1021.     *MoreFiles = '\0';
  1022.     return self;
  1023. }
  1024.  
  1025. - pack:sender {
  1026.     char c[8192];
  1027.     getText(packCmdText,c);
  1028.     if (!blank(c)) [self system:c];
  1029.     [packPanel orderOut:sender];
  1030.     if (*MoreFiles) [self packFiles:MoreFiles];
  1031.     return self;
  1032. }
  1033.  
  1034. - (int)iconReleasedAt:(double)x :(double)y ok:(int *)flag {
  1035.     [self packFiles:iconPathList];
  1036.     *flag = YES;
  1037.     return 0;
  1038. }
  1039.  
  1040. - help:sender {
  1041.     openFile(path("README.rtfd"));
  1042.     return self;
  1043. }
  1044.  
  1045. - editTable:sender {
  1046.     if (confirm("Edit the command table that controls Opener?\n(Unix experts only)"))
  1047.         System("open %s",path("Opener.table"));
  1048.     return self;
  1049. }
  1050.  
  1051. @end
  1052.